package project2;

import java.util.ArrayList;

import project1.Token;

public class Parser {
	
	/*
	 * Takes in a list of tokens returned from
	 * the LexicalAnalyzer
	 * 
	 * 
	 */
	public ArrayList<Token> tokenlist;
	private int tokenPointer = 0;
	public Domain domains;
	public Parser(ArrayList<Token> tokenlist){
		this.tokenlist = tokenlist;
		this.domains = new Domain();
	}
	/*
	 * Method to retrieve a token with a pointer 
	 */
	public Token getToken(int count){
		return tokenlist.get(count);
	}
	/*
	 * Method to retrieve the current token with a pointer 
	 */
	public Token getCurrentToken(){
		while(tokenlist.get(tokenPointer).type.equals("COMMENT"))
			incrementPointer();
		return tokenlist.get(tokenPointer);
	}
	/*
	 * Private Method to increment the token pointer
	 */
	private int incrementPointer(){
		return tokenPointer++;
	}
	/*
	 * Method to get next token and increment the pointer
	 */
	public Token popToken(){
		if(tokenlist.get(tokenPointer).type.equals("STRING"))
			domains.put(tokenlist.get(tokenPointer).value, "1");
		return tokenlist.get(incrementPointer());
	}
	/*
	 * Method to pop the next token if it is one of the tokens
	 * passed in as an argument, if not throws an exception
	 * 
	 * @parameter list of tokens
	 * 
	 * @Returns the token type popped
	 * 
	 * @Throws parse exception
	 */
	public Token popAndCheck(ArrayList<Token> tokenlist) throws ParseException{
		for (Token mytoken : tokenlist){
			if(mytoken.type.equals(getCurrentToken().type)){
				return popToken();
			}
		}
		throw new ParseException(getCurrentToken());
	}
	/*
	 * Method to pop the next token if it is one of the token types
	 * passed in as an argument, if not throws an exception
	 * 
	 * @parameter list of tokens
	 * 
	 * @Returns the token type popped
	 * 
	 * @Throws parse exception
	 */
	public Token popAndCheck(String[] tokentypes) throws ParseException{
		for (String tokentype : tokentypes){
			if(tokentype.equals(getCurrentToken().type)){
				return popToken();
			}
		}
		for (String tokenvalue : tokentypes){
			if(tokenvalue.equals(getCurrentToken().value)){
				return popToken();
			}
		}
		throw new ParseException(getCurrentToken());
	}
	
	/*
	 * -----------------------------------------------------------
	 * All the Parse Methods are below here
	 * -----------------------------------------------------------
	 */
	
	public DatalogProgram parseDatalogProgram() throws ParseException{
		return new DatalogProgram(parseSchemeList(), parseFactList(), parseRuleList(), parseQueryList());
	}
	/*
	 * <Rule List>  -> <Rule> <Rule List> | e
	 */
	public RuleList parseRuleList() throws ParseException{
		RuleList rulelist = new RuleList();
		popAndCheck(new String[]{"RULES"});
		popAndCheck(new String[]{":"});
		while(getCurrentToken().type.equals("ID")){
			rulelist.add(parseRule());
		}
		return rulelist;
	}
	/*
	 * <Rule> -> <Simple Predicate> :- <Predicate List> .
	 */
	public Rule parseRule() throws ParseException{
		return new Rule(parseSimplePredicate(),parsePredicateList());
	}
	/*
	 * <Simple Predicate> -> <Predicate Name> ( <Argument List> )
	 */
	public SimplePredicate parseSimplePredicate() throws ParseException{
		return new SimplePredicate(parseName(),parseArgList());
	}
	/*
	 <Argument List>  -> <Argument> <Argument List Tail>
	 <Argument List Tail>  -> , <Argument List> | e
	 <Argument>  -> <String> | <Identifier>
	 */
	public ArrayList<String> parseArgList() throws ParseException{
		popAndCheck(new String[]{"("});
		ArrayList<String> arglist = new ArrayList<String>();
		while(true){
			Token poppedToken = popAndCheck(new String[]{"STRING","ID"});
			arglist.add(poppedToken.value);
			poppedToken = popAndCheck(new String[]{")",","});
			if (poppedToken.value.equals(","))
				continue;
			if(poppedToken.value.equals(")"))
				break;
		}
		return arglist;
	}
	/*
	 * <Predicate List> -> <Predicate> <Predicate List Tail>
	 * <Predicate List Tail> -> , <Predicate List> | e
	 */
	public ArrayList<Predicate> parsePredicateList() throws ParseException{
		popAndCheck(new String[]{":-"});
		ArrayList<Predicate> predlist = new ArrayList<Predicate>();
		while(true){
			predlist.add(new Predicate(parseName(),parseParamList()));
			Token poppedToken = popAndCheck(new String[]{".",","});
			if (poppedToken.value.equals("."))
				break;
		}
		return predlist;
	}
	/*
	<Parameter List> -> <Parameter> <Parameter List Tail>
	<Parameter List Tail> -> , <Parameter List> | e
	 */
	public ArrayList<Parameter> parseParamList() throws ParseException{
		popAndCheck(new String[]{"("});
		ArrayList<Parameter> paramlist = new ArrayList<Parameter>();
		while(true){
			paramlist.add(parseParameter());
			Token poppedToken = popAndCheck(new String[]{")",","});
			if (poppedToken.value.equals(","))
				continue;
			if(poppedToken.value.equals(")"))
				break;
		}
		return paramlist;
	}
	/*
	<Parameter> -> <Argument> | <Comparator> | <Expression>
	 */
	public Parameter parseParameter() throws ParseException{
		Token poppedToken = this.getCurrentToken();
		if (poppedToken.type.equals("STRING") || poppedToken.type.equals("ID"))
			return new Parameter(parseArgument());
		else if(poppedToken.value.equals("("))
			return new Parameter(parseExpression());
		else 
			return new Parameter(parseComparator());
	}
	public String parseArgument() throws ParseException{
		return popAndCheck(new String[]{"STRING","ID"}).value;
	}
	public String parseExpression() throws ParseException{
		popAndCheck(new String[]{"("});
		String toreturn;
		toreturn = "("+parseParameter().toString() +  popAndCheck(new String[]{"+","*"}).value + parseParameter().toString()+")";
		popAndCheck(new String[]{")"});
		return toreturn;
	}
	public String parseComparator() throws ParseException{
		return popAndCheck(new String[]{"<","<=","=","!=",">=",">"}).value + popAndCheck(new String[]{"("}).value + parseArgument() 
		+ popAndCheck(new String[]{","}).value + parseArgument() + popAndCheck(new String[]{")"}).value;
	}
	
	/*
	 * <Query List> -> <Query> <Query List Tail>
	 * <Query List Tail> -> <Query List> | e
	 */
	public QueryList parseQueryList() throws ParseException{
		popAndCheck(new String[]{"QUERIES"});
		popAndCheck(new String[]{":"});
		QueryList querylist = new QueryList();
		while(!getCurrentToken().type.equals("EOF")){
			querylist.add(parseQuery());
			popAndCheck(new String[]{"?"});
		}
		return querylist;
	}
	/*
	 * <Query> -> <Simple Predicate> ?
	 */
	public Query parseQuery() throws ParseException{
		return new Query(parseSimplePredicate());
	}
	/*
	 * <Fact List> -> <Fact> <Fact List> | e
	 */
	public FactList parseFactList() throws ParseException{
		FactList factlist = new FactList();
		popAndCheck(new String[]{"FACTS"});
		popAndCheck(new String[]{":"});
		while(getCurrentToken().type.equals("ID")){
			factlist.add(parseFact());
		}
		return factlist;
	}
	/*
	 * <Fact> -> <Fact Name> ( <Constant List> ) .
	 */
	public Fact parseFact() throws ParseException{
		Fact tempfact = new Fact(parseName(),parseConstList());
		popAndCheck(new String[]{"."});
		return tempfact;
	}
	/*
	 <Constant List>       -> <String> <Constant List Tail>
     <Constant List Tail>  -> , <Constant List> | e
	 */
	public ArrayList<String> parseConstList() throws ParseException{
		popAndCheck(new String[]{"("});
		ArrayList<String> attrlist = new ArrayList<String>();
		while(true){
			Token poppedToken = popAndCheck(new String[]{"STRING"});
			attrlist.add(poppedToken.value);
			poppedToken = popAndCheck(new String[]{")",","});
			if (poppedToken.value.equals(","))
				continue;
			if(poppedToken.value.equals(")"))
				break;
		}
		return attrlist;
	}
	
	
	
	
	
	
	/*
	 * <Scheme List> -> <Scheme><Scheme List Tail>
	 */
	public SchemeList parseSchemeList() throws ParseException{
		SchemeList schemelist = new SchemeList();
		popAndCheck(new String[]{"SCHEMES"});
		popAndCheck(new String[]{":"});
		schemelist.add(parseScheme());
		while(getCurrentToken().type.equals("ID")){
			schemelist.add(parseScheme());
		}
		return schemelist;
	}
	/*
	 * <Scheme> -> <Scheme Name> ( <Attribute List> )
	 */
	public Scheme parseScheme() throws ParseException{
		return new Scheme(parseName(),parseAttrList());
	}
	/*
	 * <Scheme Name> -> <Identifier>
	 */
	public String parseName() throws ParseException{
		return popAndCheck(new String[]{"ID"}).value;
	}
	/*
	 * <Attribute List> -> <Attribute> <Attribute List Tail>
	 * <Attribute List Tail>  -> , <Attribute List> | E
	 */
	public ArrayList<String> parseAttrList() throws ParseException{
		popAndCheck(new String[]{"("});
		ArrayList<String> attrlist = new ArrayList<String>();
		while(true){
			Token poppedToken = popAndCheck(new String[]{"ID"});
			attrlist.add(poppedToken.value);
			poppedToken = popAndCheck(new String[]{")",","});
			if (poppedToken.value.equals(","))
				continue;
			if(poppedToken.value.equals(")"))
				break;
		}
		return attrlist;
	}
}
